import { JwtService } from '@nestjs/jwt'
import { Injectable } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { RedisService } from '@/shared/redis.service'
import { SharedService } from '@/shared/shared.service'
import { LogService } from '../monitor/log/log.service'
import { MenuService } from '../system/menu/menu.service'
import { UserService } from '../system/user/user.service'
import { ApiException, AuthEnum, CacheKey, ConfigEnum, LoginAccountDTO, TimeUnit } from '@/common'

@Injectable()
export class AuthService {
  constructor(
    private readonly jwtService: JwtService,
    private readonly logService: LogService,
    private readonly userService: UserService,
    private readonly menuService: MenuService,
    private readonly redisService: RedisService,
    private readonly sharedService: SharedService,
    private readonly configService: ConfigService,
  ) {}

  /** 获取图片验证码 */
  public async getCaptcha() {
    return this.sharedService.generateCaptcha()
  }

  /** 登录 */
  public async login(request: ExpressRequest, body: LoginAccountDTO) {
    try {
      const { uuid, captcha, username, password } = body
      // 校验验证码 & 通过验证后需删除缓存的键值
      await this.sharedService.validateCaptcha(uuid, captcha)
      const user = await this.userService.findOneByUsername(username)
      if (!user || user.status === 0) throw new ApiException(`该账号不存在或已停用`)
      // 校验账号密码 错误统一返回账号或密码错误 加强安全性
      if (!(await this.sharedService.validateHashPwd(user.password, password))) throw new ApiException(`账号或密码错误`)
      // 准备 Jwt 载荷 并生成 Token 并缓存到 Redis
      const payload: Omit<JwtPayload, 'iat' | 'exp'> = { userId: user.id, username: user.username }
      const expiresIn = this.configService.get<number>(ConfigEnum.JWT_EXPIRESIN)!
      const accessToken = this.jwtService.sign(payload)
      const accessTokenKey = `${CacheKey.ADMIN_USER_TOKEN}:${user.id}`
      await this.redisService.setEx(accessTokenKey, accessToken, expiresIn)
      await this.logService.createLoginLog(request, '登录成功', accessTokenKey)
      return { accessToken }
    } catch (error: any) {
      await this.logService.createLoginLog(request, error.message || '登录失败')
      return Promise.reject(error)
    }
  }

  /** 获取登录账号的信息 */
  public async getInfo(userId: string) {
    const user = await this.userService.findOneById(userId)
    // 获取有效的角色列表
    const activeRoles = user.roles.filter((role) => role.status === 1)
    if (activeRoles.length === 0) throw new ApiException('请联系管理员分配角色')
    // 提取角色编码和角色 ID
    const roleCodeList = activeRoles.map((role) => role.code)
    const roleIdList = activeRoles.map((role) => role.id)
    // 判断是否为管理员角色
    const isAdmin = roleCodeList.includes(AuthEnum.ADMIN_ROLE_CODE)
    const permissions = await this.menuService.findPermissionsByRoleIds(roleIdList, isAdmin) // 根据角色 id 组获取其所有的按钮权限
    // 移除不必要的字段
    const removeKeys = ['roles', 'roleIds']
    removeKeys.forEach((key) => Reflect.deleteProperty(user, key))
    // 缓存权限和角色信息
    await this.redisService.setEx(`${CacheKey.ADMIN_USER_ROLES}:${user.id}`, JSON.stringify(roleCodeList), TimeUnit.HALF_DAY)
    await this.redisService.setEx(`${CacheKey.ADMIN_USER_PERMISSIONS}:${user.id}`, JSON.stringify(permissions), TimeUnit.HALF_DAY)
    return { user, roles: roleCodeList, permissions }
  }

  /** 获取登录账号的路由表信息 */
  public async getRoutes(userId: string) {
    const data = await this.userService.findOneById(userId)
    const roleIdList = data.roles.map((role) => role.id)
    const isAdmin = data.roles.map((role) => role.code).some((roleCode) => roleCode === AuthEnum.ADMIN_ROLE_CODE)
    return this.menuService.findRoutesByRoleIds(roleIdList, isAdmin)
  }

  /** 退出登录 */
  public async logout(token: string) {
    try {
      const payload: JwtPayload = this.jwtService.verify(token)
      await this.userService.clearCacheByUserId(payload.userId)
    } catch (error) {}
    return `退出登录成功`
  }
}
